/*
 * Galaxium Messenger
 * Copyright (C) 2005-2007 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Security.Cryptography;
using System.Globalization;

using Galaxium.Core;

namespace Galaxium.Protocol.Msn
{
	public class P2PMessage
	{
		P2PBinaryHeader _header = new P2PBinaryHeader ();
		byte[] _payload = new byte[0];
		uint _footer;
		bool _complete;
		MsnSession _session;
		SLPMessage _slp = null;
		
		public P2PBinaryHeader Header
		{
			get { return _header; }
			set { _header = value; }
		}
		
		public byte[] Payload
		{
			get { return _payload; }
			set
			{
				_payload = value;
				_slp = null;
				
				_header.ChunkSize = (uint)_payload.Length;
				_header.TotalSize = Math.Max (_header.TotalSize, _header.ChunkSize);
			}
		}
		
		public uint Footer
		{
			get { return _footer; }
			set { _footer = value; }
		}
		
		public bool IsAck
		{
			get { return (_header.Flags & P2PHeaderFlag.Ack) == P2PHeaderFlag.Ack; }
		}
		
		public bool Complete
		{
			get { return _complete; }
		}
		
		public MsnSession Session
		{
			get { return _session; }
		}
		
		public SLPMessage SLPMessage
		{
			get
			{
				if (_slp == null)
					_slp = SLPMessageParser.Parse (_session, _payload);
				
				return _slp;
			}
			set
			{
				Payload = value.ToByteArray ();
				_slp = null;
			}
		}
		
		public P2PMessage (MsnSession session)
		{
			_session = session;
		}
		
		public P2PMessage (MsnSession session, byte[] data)
			: this (session)
		{
			Stream stream = new System.IO.MemoryStream (data);
			BinaryReader reader = new System.IO.BinaryReader (stream);
			
			byte[] headerData = new byte[48];
			reader.Read (headerData, 0, 48);
			
			_header = new P2PBinaryHeader (headerData);
			
			/*Console.WriteLine("[INCOMING MSNSLP] Binary Data Detected:");
			Console.WriteLine("[INCOMING MSNSLP] SessionID: "+_header.SessionID);
			Console.WriteLine("[INCOMING MSNSLP] Identifier: "+_header.Identifier);
			Console.WriteLine("[INCOMING MSNSLP] Offset: "+_header.Offset);
			Console.WriteLine("[INCOMING MSNSLP] TotalSize: "+_header.TotalSize);
			Console.WriteLine("[INCOMING MSNSLP] MessageSize: "+_header.MessageSize);
			Console.WriteLine("[INCOMING MSNSLP] Flags: "+_header.Flags);
			Console.WriteLine("[INCOMING MSNSLP] AckSessionID: "+_header.AckSessionID);
			Console.WriteLine("[INCOMING MSNSLP] AckIdentifier: "+_header.AckIdentifier);
			Console.WriteLine("[INCOMING MSNSLP] AckTotalSize: "+_header.AckTotalSize);*/
			
			_payload = new byte[_header.ChunkSize];
			stream.Read (_payload, 0, (int)_header.ChunkSize);
			
			_footer = P2PBinaryHeader.ToBigEndian (reader.ReadUInt32 ());

			//Console.WriteLine("[INCOMING MSNSLP] Footer: "+_footer);
			
			_complete = (_header.ChunkOffset + _header.ChunkSize) == _header.TotalSize;
			
			reader.Close();
			stream.Close();			
		}
		
		public P2PMessage (P2PMessage copyHeader)
		{
			_header = new P2PBinaryHeader (copyHeader.Header);
		}
		
		public P2PMessage CreateAck ()
		{
			P2PMessage ack = new P2PMessage (_session);
			
			ack.Header.SessionID = _header.SessionID;
			ack.Header.TotalSize = _header.TotalSize;
			ack.Header.Flags = P2PHeaderFlag.Ack;
			ack.Header.AckID = _header.MessageID;
			ack.Header.AckUID = _header.AckID;
			ack.Header.AckSize = _header.TotalSize;
			
			return ack;
		}
		
		public byte[] ToByteArray ()
		{
			byte[] bytes = new byte[48 + _payload.Length + 4];
			MemoryStream stream = new MemoryStream (bytes);
			BinaryWriter writer = new BinaryWriter (stream);
			
			writer.Write (_header.ToByteArray ());
			writer.Write (_payload);
			writer.Write (P2PBinaryHeader.ToBigEndian (_footer));

			writer.Close ();
			stream.Close ();
			
			return bytes;
		}
		
		public override string ToString ()
		{
			return ToString (true);
		}
		
		public string ToString (bool showText)
		{
			StringBuilder sb = new StringBuilder ();
			byte[] data = ToByteArray ();
			uint hexChars = 0;
			
			for (int i = 0; i < data.Length; i++)
			{
				string str = string.Format ("0x{0:x2} ", data[i]).ToUpper ();
				
				// Show payload as ASCII if possible
				if (showText && (i >= 48) && (i < (data.Length - 4)))
				{
					try
					{
						char c = Encoding.ASCII.GetChars (new byte[] { data[i] })[0];
						
						//TODO: is there a better way to decide which chars to dislay?
						if (char.IsLetterOrDigit (c) || char.IsPunctuation (c) || char.IsWhiteSpace (c) || (c == '<') || (c == '>') || (c == '='))
							str = c.ToString ();// + "   ";
						
						hexChars = 0;
					}
					catch
					{
						hexChars++;
					}
				}
				else
					hexChars++;
				
				sb.Append (str);
				
				if ((hexChars > 0) && (hexChars % 10 == 0))
					sb.AppendLine ();
			}
			
			return sb.ToString ();
		}
	}
}